/*
 * Title: WADS
 * Description: Web-based Auto flow Documents System
 * Copyright: 2007
 * Company: CoE
 * @author: Old developer team.
 * @version 1.1
 */

package wads.app;

import wads.drawing.line.CaseLink;
import wads.drawing.node.GateNode;
import wads.drawing.node.DocumentNode;
import wads.drawing.node.DatabaseNode;
import wads.drawing.CaseShape;
import wads.drawing.node.CaseNode;
import wads.drawing.node.UserNode;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Vector;
import javax.swing.JPanel;
import javax.swing.JViewport;

public abstract class DrawPanel extends JPanel implements MouseMotionListener, MouseListener, Printable {

  protected CaseEditor caseEditor = null;
  //the reference to the CaseEditor of this panel
  private boolean modified;
  private Vector<CaseShape> shapesDrawn = new Vector<CaseShape>();
  //vector to keep the list of shapes drawn
  private Vector<CaseShape> selectedShapes = new Vector<CaseShape>();
  //vector to keep the list of selected shapes
  private Vector selectedNodes = null;
  private Vector associatedLinks = null;
  private Rectangle selectionBounds = null;
  private Point initialCursorPoint = null;
  private Point lastCursorPoint = null;

  //for fonts
  private String fontName = "Dialog";
  private int fontStyle = 0;
  private int fontSize = 12;
  
  //type of shape TO BE drawn
  private CaseShape currentShape = null;

  //a temporary variable to hold the node that a link anchored
  private CaseNode nodeAnchored = null;

  //variable to detect if Ctrl key is currently pressed
  private boolean ctrlKeyDown = false;

  //flag to determine if moving selection
  private boolean movingSelection = false;

  private Color foregroundColor = Color.black;
  private Color backgroundColor = Color.white;

  private float zoomPercent = 1.0f;
  //the zooming scale; 1 means at 100%

  private boolean isSnapToGrid;
  private boolean isGridOn;
  private int gridSize;

  private Point pA, pB, pC;

  //Default is a square pen with a linewidth of 1,
  //  no dashing, miter segment joins
  //  and non-projecting end caps.
  //  ie. (1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
  private static final BasicStroke DEFAULT_STROKE = new BasicStroke();

  //dash array represents the dashin pattern of the stroke
  //It is set to a relatively large value so that
  //the dash is sparse enough
  //so that rendering it in Graphics2D will not be too slow.
  private static final float DRAG_DOTTED_DASH[] = {10.0f};
  private static final float GROUP_OUTLINE_STROKE_DASH[] = {15.0f, 5.0f, 3.0f, 5.0f};

  private static final BasicStroke DRAG_DOTTED_STROKE = new BasicStroke(1.0f,
      BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, DRAG_DOTTED_DASH, 0.0f);
  private static final BasicStroke GROUP_OUTLINE_STROKE = new BasicStroke(1.2f,
      BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, GROUP_OUTLINE_STROKE_DASH, 0.0f);
  private BasicStroke currentStroke = DEFAULT_STROKE;

  public DrawPanel(CaseEditor ce) {
    
    //This is the crucial step to enable scroll bar to work.
    //Should set to max resolution of screen, else user if unable to
    //exceed this size.
    setPreferredSize(new Dimension(1200, 900));

    setToolTipText(ce.getTitle());
    caseEditor = ce;
    modified = false;
    
    isSnapToGrid = false;
    isGridOn = false;
    gridSize = CaseNode.DEFAULT_NODE_ICON_WIDTH;
    
    addMouseListener(this);
    addMouseMotionListener(this);
    setForeground(Color.black);
    setBackground(Color.white);
  }
  
  abstract protected void setCurrentToolCursor(int toolkey);
  abstract public CaseShape createCurrentShape(Point anchorPoint);
  abstract public boolean isCurrentToolaNode();
  abstract public boolean isCurrentToolaLink();
  abstract public boolean isCurrentToolaGroup();
  
  /* methods for debugging */
  public void mark(int x, int y){
    Graphics g = getGraphics();
    g.setColor(Color.red);
    g.drawLine(x, y, x+10, y+10);
    g.setColor(Color.black);
  }

  public void pause(long t){
    try {
      Thread.sleep(t);
    }
    catch(InterruptedException ix) {
      ix.printStackTrace();
    }
  }

  public void pauseForKey(){
    System.out.println("Press any key to continue...");
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    try{
      br.readLine();
    }
    catch(IOException e) { e.printStackTrace(); }
  }// end methods for debugging
  
  public Color getForegroundColor(){
    return foregroundColor;
  }

  public Color getBackgroundColor(){
    return backgroundColor;
  }

  public void setForegroundColor(Color color){
    foregroundColor = color;
  }

  public void setBackgroundColor(Color color){
    backgroundColor = color;
  }

  public boolean isModified(){
    return modified;
  }

  public void setModified(boolean result){
    modified = result;
  }

  public void setDefaultCursor(){
    this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
  }

  public void changeFont(String f, int st, String si){
    Integer newSize = new Integer(si);
    int size = newSize.intValue();

    this.fontName = f;
    this.fontStyle = st;
    this.fontSize = size;
  }

  /** Override getGraphics() to clip the draw panel
   *  to the area of the visible viewport
   */
  @Override
  public Graphics getGraphics(){
    Graphics g = super.getGraphics();
    if(getParent() instanceof JViewport){
      JViewport viewPort = (JViewport)getParent();
      Rectangle viewRect = viewPort.getViewRect();
      g.clipRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height);
    }
    
    return g;
  }
  
  /** To determine if the given shape is already selected */
  public boolean isShapeSelected(CaseShape shape){
    for (int i=0; i< selectedShapes.size(); i++){
      CaseShape curShape = (CaseShape)selectedShapes.elementAt(i);
      if (shape == curShape)
        return true;
    }
    
    return false;
  }
  
  public void select(Point pressedPt){
    //Return if nothing is drawn
    if (getShapesDrawn().isEmpty())
      return;
    else{
      //Obtain the shape that the mouse cursor is currently pressed over
      CaseShape shapePressed = getShapeAt(pressedPt);
      
      //Deselect all previous selection if
      //no shape is selected and then repaint() in a later line.
      if (shapePressed == null){
        removeAllSelection();
      }
      else{//some object is pressed upon
        /* Possible scenerios when mouse pressed
         * 1. mouse pressed with Ctrl down
         * 2. mouse pressed with Ctrl not down and <=1 shapes selected
         * 3. mouse pressed with Ctrl not down and >1 shapes selected
         */

        if (this.ctrlKeyDown){
          //Determine if shapePressed is already selected
          if (isShapeSelected(shapePressed))
            //Deselect
            deselectShape(shapePressed);
          else
            //Select the shape. The highlighting will be done
            //later in paintComponent
            selectShape(shapePressed);
        }
        else{
          //if in single selection mode
          //and the current shape selected is not a previously
          //selected shape deselect all previous selection
          if (!selectedShapes.contains(shapePressed)){
            /* Don't use this condition because it won't have any effects
             * if selected shapes have more than 1 and mouse pressed on selected shape
             */
            //Remove previous selection
            removeAllSelection();

            //Select the shape.
            //highlighting will be done later in paintComponent
            selectShape(shapePressed);
          }
        }
      }
      repaint();
    }
  } //end of select(Point)

  public void selectShape(CaseShape shape){
    shape.select();
    selectedShapes.addElement(shape);
    
    //Enable cut, copy and delete
    caseEditor.cutMenuItem.setEnabled(true);
    caseEditor.copyMenuItem.setEnabled(true);
    caseEditor.deleteMenuItem.setEnabled(true);
  }
  
  public Vector<CaseShape> getSelectedShapes() {
    return selectedShapes; 
  }
  
  public void setSelectedShapes(Vector<CaseShape> vcs) {
    selectedShapes = vcs;
  }

  public void deselectShape(CaseShape shape){
    shape.deselect();
    selectedShapes.removeElement(shape);

    //Disable cut, copy and delete
    if (selectedShapes.size()==0){
      //Enable cut,copy and delete
      caseEditor.cutMenuItem.setEnabled(false);
      caseEditor.copyMenuItem.setEnabled(false);
      caseEditor.deleteMenuItem.setEnabled(false);
    }
  }
  
  @Override
  public void removeAll(){
    //Remove All Shapes drawn
    removeAllShapesDrawn();

    //Remove all selection
    removeAllSelection();
  }

  public void removeAllShapesDrawn(){
        getShapesDrawn().removeAllElements();
  }

  public void removeAllSelection(){
    selectedShapes.removeAllElements();
  }

  public CaseShape getShapeAt(Point selectedPt){
    CaseShape curShape = null;
    CaseShape shapeSelected = null;
    boolean shapeFound = false;     //flag to indicate if shape is found
    
    //loop DOWN the vector of shapes drawn to find out the selected shape.
    //The reason for looping down instead of up is because we want to select
    //the top most shape that contains the point
    int startIndex = getShapesDrawn().size() - 1;
    for(int i = startIndex; i >= 0 && !shapeFound; i--){
      curShape = (CaseShape)getShapesDrawn().elementAt(i);
      if (curShape != null){
        //Test and determine the shape that has mouse pressed
        if (curShape.isWithinScaledBounds(selectedPt, zoomPercent)){
          //Shape required is found
          shapeSelected = curShape;
          
          //Set index to out of range value to terminate loop
          shapeFound = true;
        }
      }
    } //end of Loop for

    if (shapeFound)
      return shapeSelected;
    else
      return null;
  }
  
  public Vector<CaseShape> getShapeDrawn() {
    return getShapesDrawn();
  }
  
  protected int getShapeDrawnIndex(CaseShape shape) {
    return getShapesDrawn().indexOf(shape);
  }//end of getShapeDrawnIndex()
  
  public void clearShapesDrawn() {
        getShapesDrawn().removeAllElements();
  }
  
  public void addElementToShapesDrawn(CaseShape cs) {
        getShapesDrawn().addElement(cs);
  }
  
  public void addAllToShapesDrawn(Vector<CaseShape> vcs) {
        getShapesDrawn().addAll(vcs);
  }
  
    @Override
  public void mouseEntered(MouseEvent evt){
    //Set cursor shape to that of the cursor
    setCurrentToolCursor(caseEditor.getCurrentTool());
  }

    @Override
  public void mouseExited(MouseEvent evt){
    //Set cursor shape to that of default cursor
    setDefaultCursor();
  }
  
    @Override
  public void mouseClicked(MouseEvent evt){
    int tool = caseEditor.getCurrentTool();
    if (tool == WADDiagramToolBar.SELECT_TOOL){
      try{
        //Check for double click
        if(evt.getClickCount()==2){
          //Process selection at given point
          CaseShape shape = getShapeAt(evt.getPoint());
          if(shape == null)
            return;
          
          if(shape instanceof UserNode) {
            ((CaseNode)shape).enterData(caseEditor);
          }
          else if(shape instanceof DocumentNode) {
            ((CaseNode)shape).enterData(caseEditor);
          }
          else if(shape instanceof DatabaseNode) {
              System.err.println("This tool does not have function support now.");
          }
//          else if(shape instanceof WADGroup) {
//            ((CaseNode)shape).enterData(caseEditor);
//          }
          else if(shape instanceof GateNode) {
            ((CaseNode)shape).enterData(caseEditor);
          }
          else if(shape instanceof CaseLink) {
            //System.out.println("Option Not Available now");
            ((CaseLink)shape).enterData(caseEditor, this);
          }
          else {
            System.out.println("No Response with this Action");
          }
        }
      }
      catch(Exception e) {
          System.err.println("Exception thrown while trying to access Tools action.");
        e.printStackTrace();
      }

      //Update screen with text
      repaint();
    }
  }

    @Override
  public void mousePressed(MouseEvent evt){
    //prepare for potential drag for group select
    Point pointPressed = evt.getPoint();
    Point scaledPoint = getUserPoint(pointPressed, this.zoomPercent);
    pA = scaledPoint; //start
    pB = scaledPoint; //end

    int tool = caseEditor.getCurrentTool();
    if(tool == WADDiagramToolBar.SELECT_TOOL) {
      if (evt.isControlDown())
        ctrlKeyDown = true;
      else
        ctrlKeyDown = false;
      
      //try to select object(if any) at the given pt
      select(evt.getPoint());
      
      //Update the state of the delete menu item
      caseEditor.deleteMenuItem.setEnabled(!selectedShapes.isEmpty());
    }
    else if(isCurrentToolaNode()) {
      nodeAnchored = insideNode(scaledPoint);
 //     if(nodeAnchored != null && nodeAnchored instanceof WADGroup){
        //If MousePressed in WADGroup Shape Drawn Area.
        //Process to add that node to be an element of the Group that pressed.
//        drawCurrentShape(scaledPoint);
//        if(getCurrentShape() != null && getCurrentShape() instanceof UserNode){
//          UserNode curNode = (UserNode)getCurrentShape();
//          ((WADGroup)nodeAnchored).addNode(curNode);
//          curNode.setNodeToGroupMember(true);
//          curNode.setGroupParent((WADGroup)nodeAnchored);
//          curNode.setGroupStartXYDistance(Math.abs(curNode.getStartX()-((WADGroup)nodeAnchored).getStartX()), Math.abs(curNode.getStartY()-((WADGroup)nodeAnchored).getStartY()));
//          addNewShape(curNode);
//        }
//      }
//      else{
        //In the case of MousePressed out of WADGroup Shape Drawn Area
        //Create NODE normally.
        
        //1. Creates the node and draw the node
        drawCurrentShape(scaledPoint);

        //2. Add node to the tracking list
        addNewShape(getCurrentShape());
 //     }
      
      //Inform DrawAction that the node is drawn
      //No actionEvent info is required
      caseEditor.drawAction.actionPerformed(null);
      
      //Always reset current tool back to select after it is done.
      //This is the standard treatment in the two case tools.
      //Note: since this resets currentShape to null,
      //drawAction.actionPerformed is called must be called before this
      nodeAnchored = null;
      caseEditor.setCurrentTool(WADDiagramToolBar.SELECT_TOOL);
      setCurrentShape(null);
      setCurrentToolCursor(caseEditor.getCurrentTool());
      setModified(true);
    }
    else if(isCurrentToolaLink()){
      //Note: Link is not drawn immediately when mousePressed.
      //It is drawn only when the link is rubberbanded/dragged.

      //Check this point is within a node
      nodeAnchored = insideNode(scaledPoint);
      if (nodeAnchored != null){
        //If valid then instantiate current link at point evt.getPoint();
        CaseLink newLink = (CaseLink)createCurrentShape(scaledPoint);

        //Error: Link required has not yet being defined
        if (newLink == null){
          System.out.println("Link type not defined!");
          return;
        }

        //Make this link the current selected shape
        setCurrentShape(newLink);

        //Add link to the tracking list;
        //To remove in mouseReleased if it is not valid
        addNewShape(getCurrentShape());

        setModified(true);
      }
    }
//    else if(isCurrentToolaGroup()){
//      //Note: Group is not drawn immidietly when mousePressed
//      //it will drawn when mouseRelease Event occur
//
//      //Set Group as Current tool
//      WADGroup newGroup = (WADGroup)createCurrentShape(scaledPoint);
//      setCurrentShape(newGroup);
//      
//      addNewShape(getCurrentShape());
//
//      //Always reset current tool back to select after it is done.
//      //This is the standard treatment in the two case tools.
//      //Note: since this resets currentShape to null,
//      //drawAction.actionPerformed is called must be called before this
//      setModified(true);
//    }//end if
  }//end of mousePressed
  
    @Override
  public void mouseReleased(MouseEvent evt) {
    //Obtain scaled(user) coordinates from mouse(screen) coordinate
    Point pointReleased = evt.getPoint();
    Point scaledPoint = getUserPoint(pointReleased, this.zoomPercent);
    int tool = caseEditor.getCurrentTool();
    if (pB.x != pA.x && tool == WADDiagramToolBar.SELECT_TOOL){
      //remove the dotted selection box used for group select
      Graphics2D g2d = (Graphics2D)getGraphics();
      g2d.scale(zoomPercent, zoomPercent);
      int rx, ry, rectWidth, rectHeight;
      rx = pA.x < pB.x?pA.x:pB.x;
      ry = pA.y < pB.y?pA.y:pB.y;
      rectWidth = Math.abs(pB.x - pA.x);
      rectHeight = Math.abs(pB.y - pA.y);
      //clear old line
      g2d.setStroke(DRAG_DOTTED_STROKE);
      //Get selection bounds
      Rectangle oldDragBounds = new Rectangle(rx, ry, rectWidth+1, rectHeight+1);
      //Erase box at old location
      g2d.setXORMode(getBackgroundColor());
      g2d.drawRect(oldDragBounds.x, oldDragBounds.y, oldDragBounds.width, oldDragBounds.height);
      //select the nodes
      for(int i=0; i<getShapesDrawn().size(); i++){
        CaseShape tmpShape = getShapesDrawn().elementAt(i);
        if (tmpShape != null){
          if (tmpShape instanceof CaseNode){
            CaseNode tmpNode = (CaseNode)tmpShape;
            if((tmpNode.getStartX() > rx && tmpNode.getStartY() > ry) &&
                ((tmpNode.getStartX() + tmpNode.getWidth()) < rx+rectWidth) &&
                ((tmpNode.getStartY() + tmpNode.getHeight()) < ry+rectHeight)){
              System.out.println("begin select");
              selectShape(tmpShape);
              repaint();
            }
          }
        }
      }
    }
    //P'Thon said that process to add node(s) to group must be draw group shape before
    //draw node(s) inside later. So the condition below are removed.
    /*else if(pB.x != pA.x && tool == WADDiagramToolBar.GROUP_TOOL){
      int rx, ry, rectWidth, rectHeight;
      rx = pA.x < pB.x?pA.x:pB.x;
      ry = pA.y < pB.y?pA.y:pB.y;
      rectWidth = Math.abs(pB.x - pA.x);
      rectHeight = Math.abs(pB.y - pA.y);
      WADGroup curGroup = (WADGroup)getCurrentShape();
      for(int i=0; i<shapesDrawn.size(); i++){
        CaseShape tmpShape = shapesDrawn.elementAt(i);
        if (tmpShape != null){
          if (tmpShape instanceof CaseNode){
            CaseNode tmpNode = (CaseNode)tmpShape;
            if((tmpNode.getStartX() > rx && tmpNode.getStartY() > ry) &&
                ((tmpNode.getStartX() + tmpNode.getWidth()) < rx+rectWidth) &&
                ((tmpNode.getStartY() + tmpNode.getHeight()) < ry+rectHeight) &&
                tmpNode instanceof UserNode){
              System.out.println("Add shape to GROUP");
              curGroup.addNode((UserNode)tmpNode);
            }
          }
        }
      }
    }*/

    if ((tool == WADDiagramToolBar.SELECT_TOOL) && (movingSelection == true)){
      /* Move selection */
      
      //Remove the selection box
      //But this becomes unnecessary because repaint() is going
      //to be called
      //As selection box drawing is not part of paintComponent,
      //it will not be drawn. But selectionBounds has to be reset.
      resetSelectionBounds();

      /* Translate the shapes that can be moved to the new
       * location and translate the associatedlinks to the
       * new location.  Careful when translating the links.
       * Only the point that is anchored on the node that is
       * moved is translated. Moreover, the order which the
       * nodes and links are drawn must follow that in the
       * original.  This is to preserve the layering of the
       * nodes and links.
       * 
       * Algo :
       *   Set up a loop move the selected nodes.
       *   The move function of the nodes will refresh the links.
       *   
       * However, this algo though simple may not be efficient
       * because the same link may be refreshed multiple times!!
       */
      
      //Set back to default stroke
      Graphics2D g2d = (Graphics2D)getGraphics();
      g2d.scale(zoomPercent, zoomPercent);
      g2d.setStroke(DEFAULT_STROKE);

      //Reset flag
      movingSelection = false;

      //Move all nodes and associated links to their new point
      Point newCursorPoint = scaledPoint;
      int dx = newCursorPoint.x - initialCursorPoint.x;
      int dy = newCursorPoint.y - initialCursorPoint.y;
      
      for(int i=0; i<selectedNodes.size(); i++) {
        CaseNode curNode = (CaseNode)selectedNodes.elementAt(i);
        /*if(curNode instanceof WADGroup) {
          curNode.move(this, snapToGrid(new Point(curNode.getStartX()+dx, curNode.getStartY()+dy)));
          ((WADGroup)curNode).refreshNodesMember();
        }
        else if(curNode instanceof UserNode) {
          if(((UserNode)curNode).isGroupMember()){
            if(((UserNode)curNode).getGroupParent().isOutsideGroupOwnerBounds(scaledPoint)){
              repaint();
              return;
            }
            else {
              curNode.move(this, snapToGrid(new Point(curNode.getStartX()+dx, curNode.getStartY()+dy)));
              ((UserNode)curNode).setGroupStartXYDistance(
                  Math.abs(curNode.getStartX()-((UserNode)curNode).getGroupParent().getStartX()),
                  Math.abs(curNode.getStartY()-((UserNode)curNode).getGroupParent().getStartY()));
            }
          }
          else {
            curNode.move(this, snapToGrid(new Point(curNode.getStartX()+dx, curNode.getStartY()+dy)));
          }
        }
        else*/
          //curNode.moveBy(dx,dy); - replace by grid snapping
          curNode.move(this, snapToGrid(new Point(curNode.getStartX()+dx, curNode.getStartY()+dy)));
      }

      //Repaint the screen after final movement
      repaint();
    }
    else {
      //in the case that mouse dragged
      //Check if it is drawing of link
      
      //If there is a node anchored => it is a link and
      //current node is different from anchored node
      //else remove it from shapesDrawn vector
      CaseShape tmpShape = getCurrentShape();
      if(tmpShape instanceof CaseLink) {
        if(!((CaseLink)tmpShape).isConfirmed()) {
          //Check this point is within a node
          CaseNode nodeEnded = insideNode(scaledPoint);
          
          /*if(nodeEnded instanceof UserNode) {
            if(((UserNode)nodeEnded).isGroupMember()) {
              repaint();
              return;
            }
          }*/
          
          //Check that anchored and ended nodes are different
          if ((nodeEnded != null) && (nodeAnchored != nodeEnded)) {
            if(nodeAnchored instanceof UserNode && nodeAnchored.getConnectedLinksSize() >= 2) {
                            getShapesDrawn().removeElement(tmpShape);
              //JOptionPane.showMessageDialog(caseEditor, "Node element can't have more 2 connections.", "Connection restriction", JOptionPane.ERROR_MESSAGE);
              caseEditor.setWrkMsg("Node element can't have more 2 connections.", Color.RED);
              repaint();
            }
            else if(nodeAnchored instanceof DocumentNode && nodeAnchored.getConnectedLinksSize() >= 2) {
                            getShapesDrawn().removeElement(tmpShape);
              //JOptionPane.showMessageDialog(caseEditor, "DocumentNode element can't have more 2 connections.", "Connection restriction", JOptionPane.ERROR_MESSAGE);
              caseEditor.setWrkMsg("Document element can't have more 2 connections.", Color.RED);
              repaint();
            }
            else if(nodeAnchored instanceof DatabaseNode && nodeAnchored.getConnectedLinksSize() >= 2) {
                            getShapesDrawn().removeElement(tmpShape);
              //JOptionPane.showMessageDialog(caseEditor, "DatabaseNode element can't have more 2 connections.", "Connection restriction", JOptionPane.ERROR_MESSAGE);
              caseEditor.setWrkMsg("Database element can't have more 2 connections.", Color.RED);
              repaint();
            }
            else {
              //Typecast tmpShape back to CaseLink so that
              //CaseLink methods could be called
              CaseLink curLink = (CaseLink)tmpShape;
            
              //Create the link using start and end nodes
              curLink.createLink(nodeAnchored, nodeEnded);
              
              //Repaint the drawing area
              repaint();
            
              //Inform Draw action that the link is drawn
              //so that it can setup necessary steps for undo
              //later, if necessary.
              //Giving actionevent event_type as -1 coz no type
              //is defined at the moment
              caseEditor.drawAction.actionPerformed(new ActionEvent(this, -1, "Draw Link"));
            }
          }
          else{
            //and remove it from the vector and repaint
                        getShapesDrawn().removeElement(tmpShape);
            repaint();
          }
          
          //Reset nodeAnchored and nodeEnded
          nodeAnchored = null;
          nodeEnded = null;

          //Note : For link, the process only ends when the
          //mouse is released.
          //Only then we reset the tool back to select and reset
          //the shape to null
          //Always reset current tool back to select after done.
          //This is the standard treatment in the two case tools.

          caseEditor.setCurrentTool(WADDiagramToolBar.SELECT_TOOL);
          setCurrentShape(null);
          setCurrentToolCursor(caseEditor.getCurrentTool());
        }
      }
      /*else if(tmpShape instanceof WADGroup) {
        WADGroup curGroup = (WADGroup)tmpShape;
        curGroup.setWidth(Math.abs(curGroup.getStartX()-scaledPoint.x));
        curGroup.setHeight(Math.abs(curGroup.getStartY()-scaledPoint.y));
        
        //If Released Point is not go from LEFT to RIGHT
        //or from UP to DOWN. We must set new Start Coordinate Point
        if(curGroup.getStartX() > scaledPoint.x)
          curGroup.setStartX(scaledPoint.x);
        if(curGroup.getStartY() > scaledPoint.y)
          curGroup.setStartY(scaledPoint.y);

        caseEditor.drawAction.actionPerformed(null);
        
        repaint();
        
        //clear anything to normal or initialize status
        caseEditor.setCurrentTool(WADDiagramToolBar.SELECT_TOOL);
        setCurrentShape(null);
        setCurrentToolCursor(caseEditor.getCurrentTool());
      }*/
    }
    repaint();
  }//end of mouseReleased

    @Override
  public void mouseMoved(MouseEvent evt){
    Point pointMoved = evt.getPoint();
    Point scaledPoint = getUserPoint(pointMoved, this.zoomPercent);
    CaseShape shapeEntered = getShapeAt(scaledPoint);
    if (shapeEntered == null)
      setToolTipText(caseEditor.getTitle());
    else
      setToolTipText(shapeEntered.getName());
    //following for updating the pointer location in the status window
    CaseNode nodePointed = insideNode(scaledPoint);
    if (nodePointed != null)
      nodePointed.onMouseMove(caseEditor, evt);
    else
      caseEditor.setDynaMsg("X=" + scaledPoint.x + " Y=" + scaledPoint.y);
    
  }//end mouseMoved
  
    @Override
  public void mouseDragged(MouseEvent evt){
    //Obtain the scaled(user) coordinates from the mouse(screen) coordinate
    Point pointDragged = evt.getPoint();
    Point scaledPoint = getUserPoint(pointDragged, this.zoomPercent);
    //mouseMoved(evt);  //simply display some messages

    if (caseEditor.getCurrentTool() == WADDiagramToolBar.SELECT_TOOL){
      if (selectedShapes.size() != 0){
        movingSelection(scaledPoint);

        /* This indicates a move action occurs.
         * The selected nodes will be moved. The links connecting these
         * nodes will also be moved.
         * Note:  Links themselves may not be moved directly.
         * Links are moved by moving the nodes which they are connected.
         * As a result of this condition, a separate vector
         * that stores selected nodes and selected
         * links may help to speed up the moving of
         * nodes and asscoiated links.
         */
      }
      else{
        //no nodes selected but draged => going to group select
        //draw a dotted outline box around
        //the items to be selected
        Graphics2D g2d = (Graphics2D)getGraphics();
        g2d.scale(zoomPercent, zoomPercent);
        //calculate this point
        pC = scaledPoint;
        int rx, ry, rectWidth, rectHeight;
        rx = pA.x < pB.x?pA.x:pB.x;
        ry = pA.y < pB.y?pA.y:pB.y;
        rectWidth = Math.abs(pB.x - pA.x);
        rectHeight = Math.abs(pB.y - pA.y);
        //clear old line
        g2d.setStroke(DRAG_DOTTED_STROKE);
        //Get selection bounds
        Rectangle oldDragBounds = new Rectangle(rx, ry, rectWidth+1, rectHeight+1);
        //Erase box at old location
        g2d.setXORMode(getBackgroundColor());
        g2d.drawRect(oldDragBounds.x, oldDragBounds.y, oldDragBounds.width, oldDragBounds.height);
        //Set new location
        rx = pA.x < pC.x?pA.x:pC.x;
        ry = pA.y < pC.y?pA.y:pC.y;
        rectWidth = Math.abs(pC.x - pA.x);
        rectHeight = Math.abs(pC.y - pA.y);
        Rectangle newDragBounds = new Rectangle(rx, ry, rectWidth+1, rectHeight+1);
        //Draw box at new location
        g2d.drawRect(newDragBounds.x, newDragBounds.y, newDragBounds.width, newDragBounds.height);
        pB = pC;
      }
    }
    else if(caseEditor.getCurrentTool() == WADDiagramToolBar.GROUP_TOOL){
      //if tool selected is GROUP TOOL it neccessary that you want square outline
      //to see what GROUP SHAPE SQUARE will be when you drawn
      Graphics2D g2d = (Graphics2D)getGraphics();
      g2d.scale(zoomPercent, zoomPercent);
      //calculate this point
      pC = scaledPoint;
      int rx,ry,rectWidth, rectHeight;
      rx = pA.x < pB.x?pA.x:pB.x;
      ry = pA.y < pB.y?pA.y:pB.y;
      rectWidth = Math.abs(pB.x - pA.x);
      rectHeight = Math.abs(pB.y - pA.y);
      //clear old line
      g2d.setStroke(GROUP_OUTLINE_STROKE);
      //Get selection bounds
      Rectangle oldDragBounds = new Rectangle(rx, ry, rectWidth+1, rectHeight+1);
      //Erase box at old location
      g2d.setXORMode(getBackgroundColor());
      g2d.drawRect(oldDragBounds.x, oldDragBounds.y, oldDragBounds.width, oldDragBounds.height);
      //Set new location
      rx = pA.x < pC.x?pA.x:pC.x;
      ry = pA.y < pC.y?pA.y:pC.y;
      rectWidth = Math.abs(pC.x - pA.x);
      rectHeight = Math.abs(pC.y - pA.y);
      Rectangle newDragBounds = new Rectangle(rx, ry, rectWidth+1, rectHeight+1);
      //Draw box at new location
      g2d.drawRect(newDragBounds.x, newDragBounds.y, newDragBounds.width, newDragBounds.height);
      pB = pC;
    }
    else{
      /* When mouse drag, if it is not move (ie select is on) then
       * it must be resizing a node or link
       * Note: the drawing of link will be done when mouseDragged
       * Link drawn should be dotted until "confirmed".
       * The resize functions for node is yet to be implemented.
       * To be done after all the functions that are previous in the old
       * jcase are implemented.
       */
      
      CaseShape tmpShape = getCurrentShape();
      if (tmpShape !=null)
        tmpShape.resize(this, scaledPoint);
    }
  }//end of mouseDragged()
  
  public Point getUserPoint(Point aPoint, float scaleFactor){
    Point scaledPoint = new Point();
    scaledPoint.x = Math.round(aPoint.x / scaleFactor);
    scaledPoint.y = Math.round(aPoint.y / scaleFactor);
    return scaledPoint;
  }
      
  private Vector getSelectedNodes(Vector selectedShapes){
    Vector<CaseShape> _selectedNodes = new Vector<CaseShape>();
    for (int i=0;i<selectedShapes.size();i++){
      CaseShape selected = (CaseShape)selectedShapes.elementAt(i);
      if (selected instanceof CaseNode)
        _selectedNodes.addElement(selected);
    }
    return _selectedNodes;
  }

  private Vector getAssociatedLinks(Vector selectedNodes){
    Vector<CaseLink> _associatedLinks = new Vector<CaseLink>();
    for (int i=0;i<selectedNodes.size();i++){
      CaseShape curNode = (CaseShape)selectedNodes.elementAt(i);
      if (curNode instanceof CaseNode){
        for (int j=0; j<((CaseNode)curNode).getConnectedLinksSize(); j++){
          CaseLink curLink = ((CaseNode)curNode).getConnectedLink(j);
          if (!_associatedLinks.contains(curLink))
            _associatedLinks.addElement(curLink);
        }
      }
    }
    return _associatedLinks;
  }
  
  private Rectangle getSelectionBounds(){
    return selectionBounds;
  }

  private void initializeSelectionBounds(Vector selectedNodes){
    //Set the smallest and largest to suitable arbitary value
    //for getting the appropriate smallest and largest point of bounds
    Point smallest = new Point(32767, 32767); //set a pt large enough
    Point largest = new Point(0,0); //set a pt small enough
    
    for (int i=0; i < selectedNodes.size();i++){
      if (selectedNodes.elementAt(i) instanceof CaseNode){
        CaseNode curNode = (CaseNode)selectedNodes.elementAt(i);

        //Get smallest x
        if (curNode.getStartX() < smallest.x)
          smallest.x = curNode.getStartX();
        
        //Get smallest y
        if (curNode.getStartY() < smallest.y)
          smallest.y = curNode.getStartY();
        
        //Get largest x
        if (curNode.getEndX() > largest.x)
          largest.x = curNode.getEndX();
        
        //Get largest y
        if (curNode.getEndY() > largest.y)
          largest.y = curNode.getEndY();
      }
    }

    //Set selection bounds
    selectionBounds = new Rectangle(smallest.x,smallest.y,
        largest.x-smallest.x+1,largest.y-smallest.y+1);
  }

  private void resetSelectionBounds(){
    selectionBounds = null;
  }

  private void updateSelectionBounds(Point newPoint){
    if (selectionBounds == null)
      return;
    //invalid modification - bounds not initialized

    selectionBounds.x = newPoint.x;
    selectionBounds.y = newPoint.y;
  }

  /*private void eraseSelection(Vector selection){
    if (selection != null)
      for (int i=0; i<selection.size(); i++){
        CaseShape shape = (CaseShape)selection.elementAt(i);
        shape.erase(this);
      }
  }*/

  private void moveSelectionBounds(Point newPoint){
    Graphics2D g2d = (Graphics2D)getGraphics();
    g2d.scale(zoomPercent, zoomPercent);

    if (currentStroke == DEFAULT_STROKE)
      //Set to dotted stroke
      g2d.setStroke(DRAG_DOTTED_STROKE);

    //Get selection bounds
    Rectangle oldSelectionBounds = getSelectionBounds();

    /* Erasing box at old location also result in erasing the
     * background objects ie.  those objects that are already
     * drawn. So comment these two lines and replace it with
     * repaint() to redraw background and as a result erasing
     * the old box
     */
    
    //Erase box at old location
    g2d.setXORMode(getBackgroundColor());
    g2d.drawRect(oldSelectionBounds.x,oldSelectionBounds.y,
        oldSelectionBounds.width,oldSelectionBounds.height);
    
    //Set new location
    updateSelectionBounds(newPoint);

    //Draw box at new location
    g2d.drawRect(selectionBounds.x, selectionBounds.y, selectionBounds.width, selectionBounds.height);
  }//end of moveSelectionBounds()
  
  private void drawSelectionBounds(){
    Graphics2D g2d = (Graphics2D)getGraphics();
    g2d.scale(zoomPercent,zoomPercent);

    if (currentStroke == DEFAULT_STROKE)
      //Set to dotted stroke
      g2d.setStroke(DRAG_DOTTED_STROKE);

    g2d.setXORMode(getBackgroundColor());
    g2d.drawRect(selectionBounds.x,selectionBounds.y,
        selectionBounds.width,selectionBounds.height);
  }//end of drawSelectionBounds()

  private void movingSelection(Point newCursorPoint){
    if (selectedShapes.size() != 0){
      //Get selected nodes
      selectedNodes = getSelectedNodes(selectedShapes);

      //Determine if selection bounds has been setup
      //If this is the first time, erase the selection also
      //to prepare for the movement of the selection box
      if (selectionBounds == null){
        if (selectedNodes == null){
          System.out.println("No nodes selected.");
          return;
        }

        //Get associated links
        associatedLinks = getAssociatedLinks(selectedNodes);
        System.out.println(associatedLinks);

        //Initialize selection bounds
        initializeSelectionBounds(selectedNodes);
          
        drawSelectionBounds();
          
        //Set moving flag to true
        movingSelection = true;

        //Keeps this cursor pt to enable to final movement
        //selection
        initialCursorPoint = newCursorPoint;

        //Keeps this cursor pt for reference of
        //drawing of next selection box
        lastCursorPoint = newCursorPoint;
      }
      else{
        //Compute new origin of the selection boundary
        int dx = newCursorPoint.x - lastCursorPoint.x;
        int dy = newCursorPoint.y - lastCursorPoint.y;
        Point newBoundsOrigin = new Point(selectionBounds.x + dx, selectionBounds.y + dy);

        //Update last cursor point ie. this point
        lastCursorPoint = newCursorPoint;
          
        //move the boundary box
        moveSelectionBounds(newBoundsOrigin);
      }
    }
  }//end of movingSelection

  /*private void eraseSelectionBox(){
    Graphics g = getGraphics();

    //Get selection bounds
    Rectangle oldSelectionBounds = getSelectionBounds();

    //Erase box at old location
    g.setXORMode(getBackgroundColor());
    g.drawRect(oldSelectionBounds.x, oldSelectionBounds.y, oldSelectionBounds.width, oldSelectionBounds.height);
  }//end of eraseSelectionBox()*/

  public void drawCurrentShape(Point anchorPoint){
    //Get graphics context
    Graphics2D g2d = (Graphics2D)getGraphics();

    //Use the current font type, size, style.
    Font font;
    font = new Font(fontName, fontStyle, fontSize);
    g2d.setFont(font);
    
    //Scale equally at both and x- and y- axis
    g2d.scale(zoomPercent, zoomPercent);

    //Create the shape and set current shape to shape created
    currentShape = createCurrentShape(anchorPoint);

    //Draw the shape created
    if (currentShape != null)
      currentShape.draw(this, g2d);
  }//end of drawCurrentShape()

  public CaseShape getCurrentShape(){
    return currentShape;
  }//end of getCurrentShape

  public void setCurrentShape(CaseShape caseShape){
    currentShape = caseShape;
  }//end of setCurrentShape()

  public void addDocShape(CaseShape docShape){
    if ((docShape != null) && (!shapesDrawn.contains(docShape)))
        getShapesDrawn().addElement(docShape);
  }//end of addDocShape()

  public void addNewShape(CaseShape newShape) {
    //Add the new shape if the shape is not null and it is
    //not already in the vector
    if ((newShape != null) && (!shapesDrawn.contains(newShape)))
        getShapesDrawn().addElement(newShape);
  }//end of addNewShape()

  public void addNewShapeAt(CaseShape newShape, int index){
    //Add the new shape if the shape is not null and it is
    //not already in the vector
    if ((newShape != null) && (!shapesDrawn.contains(newShape)))
        getShapesDrawn().insertElementAt(newShape, index);
  }//end of addNewShapeAt()

  public void clearScreen(Graphics g){
    //Clear panel
    super.paintComponent(g);
  }

  @Override
  public void paintComponent(Graphics g){
    clearScreen(g);
    drawGrid(g);
    doPaint(g);
  }//end of paintComponent()

  public void doPaint(Graphics g){
    Graphics2D g2d = (Graphics2D)g;
    //Note:
    //The scaling can actually be done when the zoom is selected.
    //It need only to be set once and it will be reflected here
    //This will be more efficient as scalli

    //Scale equally at both and x- and y- axis
    g2d.scale(zoomPercent,zoomPercent);

    Font font;
    font = new Font(fontName, fontStyle, fontSize);
    g2d.setFont(font);
    
    int size = getShapesDrawn().size();
    
    //Paint all shapes drawn
    for (int i=0; i<size; i++){
      CaseShape tmpShape = (CaseShape)getShapesDrawn().elementAt(i);
      if (tmpShape != null){
        tmpShape.draw(this,g);

        //If the shape is selected, highlight it
        if(isShapeSelected(tmpShape))
          tmpShape.highlight(g);
      }//end if
    }//end for
  }//end doPaint

  public boolean isInsideNode(Point pt){
    int size = getShapesDrawn().size();
    for (int i=0; i<size; i++){
      CaseShape tmpShape = (CaseShape)getShapesDrawn().elementAt(i);
      if (tmpShape != null){
        if (tmpShape instanceof CaseNode){
          CaseNode tmpNode = (CaseNode)tmpShape;
          if (tmpNode.contains(pt) == true)
            return true;
        }
      }
    }
    return false;
  }//end of isInsideNode()
  
  public CaseNode insideNode(Point pt){
    int size = getShapesDrawn().size();
    for (int i=0; i<size; i++){
      CaseShape tmpShape = (CaseShape)getShapesDrawn().elementAt(i);
      if (tmpShape != null){
        if (tmpShape instanceof CaseNode){
          CaseNode tmpNode = (CaseNode)tmpShape;
          if (tmpNode.contains(pt) == true)
            return tmpNode;
        }
      }
    }
    return null;
  }//end of insideNode()

  public void zoom(int percent){
    zoomPercent = percent/100.0f;
    if (zoomPercent <= 0){
      System.out.println("Invalid zoom percentage: "+percent+"<=0%");
      return;
    }
    repaint();
  }//end of zoom()

  public void delete(CaseNode node){
    //Obtain the links of the node
    Vector connectedLinks = node.getConnectedLinks();

    /* Due the deletion of link during each loop, the size of the
     * connectedLinks vector is actually shrinking by 1 every
     * turn ie. the elements are shifted towards by -1 at
     * every turn. So the next element will always be at
     * element 0 after each loop/turn.
     */

    //Obtain the initial size of vector before any deletion
    int initSize = connectedLinks.size();

    //Remove these connected links
    for (int i=0;i<initSize;i++){
      //The next element will always be at 0 due to
      //the deletion of link at 0.
      CaseLink link = (CaseLink)connectedLinks.elementAt(0);

      //delete the link
      delete(link);
    }

    //Notify caseEditor also that the node is going to be removed
    caseEditor.deleteNodeAction.actionPerformed(new ActionEvent(node, -1, "Delete node"));

    //Remove the node
        getShapesDrawn().removeElement(node);

    //Refresh the screen after deletion
    //repaint();
  }

  public void delete(CaseLink link){
    /* Deleting means more than simply deleting the selected shapes
     * When deleting a link, the associatedLinks in nodes attached
     * must also be deleted.
     */

    //Notify caseEditor that link is going to be removed
    caseEditor.deleteLinkAction.actionPerformed(new ActionEvent(link,-1,"Delete link"));

    //Need to delete references to nodes ???? Perhaps not,
    //to enable easy undo.
    //But watch out that the nodes restored are clones and the old
    //references may not be pointing to clones, or should it ???
    
    //Notify the nodes anchored that link is going to be deleted
    link.notifyDeletion();

    //Remove link from shapesDrawn
        getShapesDrawn().removeElement(link);
  }//end of delete()
  
  /*public void delete(CaseGroup group){
    //Obtain links that connected to the GROUP
    Vector connectedlinks = group.getConnectedLinks();
    
    //Obtain links size to initialize deletion counter
    int linksize = connectedlinks.size();
    for(int i=0;i>linksize;i++){
      CaseLink link = (CaseLink)connectedlinks.elementAt(0);
      delete(link);
    }
    
    //Notify caseEditor that group is going to be removed
    caseEditor.deleteGroupAction.actionPerformed(new ActionEvent(group, -1, "Delete Group"));
    
    //Remove the Group
    shapesDrawn.removeElement(group);
  }*/

    @Override
  public int print(Graphics g, PageFormat pf, int pi) throws PrinterException{
    if (pi >= 1) {
      return Printable.NO_SUCH_PAGE;
    }
    doPaint(g);
    return Printable.PAGE_EXISTS;
  }//end of print()

  public Point snapToGrid(Point p){
    int gridsize = getGridSize();

    return isSnapToGrid() ? new Point(((int)((float)p.x / gridsize)) * gridsize, ((int)((float)p.y / gridsize)) * gridsize) : p;
  }//end of snapToGrid()

  public void setSnapToGrid(boolean isSnapToGrid){
    this.isSnapToGrid = isSnapToGrid;
  }

  public boolean isSnapToGrid() { return isSnapToGrid; }

  public void setDrawGrid(boolean isGridOn){
    this.isGridOn = isGridOn;
  }

  public boolean isDrawGrid() { return isGridOn; }

  public void setGridSize(int gridSize){
    this.gridSize = gridSize;
  }

  public int getGridSize() { return gridSize; }

  public void drawGrid(){
    drawGrid(getGraphics());
  }

  public void drawGrid(Graphics g){
    if (!isDrawGrid()) return;

    Graphics2D g2d = (Graphics2D) g;
    Color originalColor = g2d.getColor();
    g2d.setColor(Color.lightGray);

    // draw horizontal grid lines
    for (int y = 0; y < getSize().height; y+=getGridSize()){
      g2d.drawLine(0,Math.round(y*zoomPercent), getSize().width, Math.round(y*zoomPercent));
    }

    // draw vertical grid lines
    for (int x = 0; x < getSize().width; x+=getGridSize()){
      g2d.drawLine(Math.round(x*zoomPercent), 0, Math.round(x*zoomPercent), getSize().height);
    }
    g2d.setColor(originalColor);
  }//end of drawGrid()

    /**
     * @return the shapesDrawn
     */
    public Vector<CaseShape> getShapesDrawn() {
        return shapesDrawn;
    }

    /**
     * @param shapesDrawn the shapesDrawn to set
     */
    public void setShapesDrawn(Vector<CaseShape> shapesDrawn) {
        this.shapesDrawn = shapesDrawn;
        this.repaint();
    }
} //end class DrawPanel
